﻿namespace _4InARow
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    public class GameLogic
    {
        private const uint k_boardMinWidth = 4;
        private const uint k_boardMaxWidth = 8;
        private const uint k_boardMinHeight = 4;
        private const uint k_boardMaxHeight = 8;
        public const uint k_PlayerNumber1 = 1;
        public const uint k_PlayerNumber2 = 2;

        private Board m_Board;
        private Player m_Player1;
        private Player m_Player2;
        private Player m_CurrentPlayer;

        public uint BoardMinWidth 
        {
            get
            {
                return k_boardMinWidth;
            }
        }

        public uint BoardMaxWidth
        {
            get
            {
                return k_boardMaxWidth;
            }
        }

        public uint BoardMinHeight
        {
            get
            {
                return k_boardMinHeight;
            }
        }

        public uint BoardMaxHeight
        {
            get
            {
                return k_boardMaxHeight;
            }
        }

        public Board Board 
        {
            get
            {
                return m_Board;
            }

            set
            {
                m_Board = value;
            }
        }

        public Player Player1 
        {
            get
            {
                return m_Player1;
            }

            set
            {
                m_Player1 = value;
            }
        }

        public Player Player2
        {
            get
            {
                return m_Player2;
            }

            set
            {
                m_Player2 = value;
            }
        }

        public Player CurrentPlayer
        {
            get
            {
                return m_CurrentPlayer;
            }
        }

        public uint CurrentPlayerNumber 
        {
            get
            {
                return m_CurrentPlayer.Number;
            }
        }

        public GameLogic()
        {
            m_Player1 = new Player(Player.ePlayerType.Human, k_PlayerNumber1);
            m_CurrentPlayer = m_Player1;
        }

        public bool AreLegalDimensions(uint i_width, uint i_height)
        {
            return i_width >= BoardMinWidth && i_width <= BoardMaxWidth && i_height >= BoardMinHeight && i_height <= BoardMaxHeight;
        }

        public void ChangeCurrentPlayer()
        {
            m_CurrentPlayer = m_CurrentPlayer == m_Player1 ? m_Player2 : m_Player1;
        }

        public void ResetCurrentPlayer()
        {
            m_CurrentPlayer = m_Player1;
        }

        public void MakeAutomaticMove(Player i_Player, out uint? o_Column, out uint? o_Row)
        {
            o_Column = o_Row = null;
            int highestMoveRank = 1;
            int currentMoveRank = 1;
            string errorMessage = string.Empty;
            List<Point> bestMoves = new List<Point>();

            if (!m_Board.IsFull())
            {
                foreach (Point move in m_Board.AvailableMoves)
                {
                    currentMoveRank = rankFutureMove(i_Player, move);

                    if (currentMoveRank > highestMoveRank)
                    {
                        bestMoves = new List<Point>();
                        bestMoves.Add(move);
                        highestMoveRank = currentMoveRank;
                    }
                    else if (currentMoveRank == highestMoveRank)
                    {
                        bestMoves.Add(move);
                    }
                }

                if (bestMoves.Count > 0)
                {
                    int randomStartingColumn = new Random().Next(1, bestMoves.Count);
                    o_Column = bestMoves[randomStartingColumn - 1].X + 1;
                    o_Row = bestMoves[randomStartingColumn - 1].Y + 1;
                }
            }
        }

        public bool CheckIfCurrentPlayerWon(uint i_LastMoveColumn, uint i_LastMoveRow)
        {
            bool playerWon = false;

            int rankOfMove = rankMove(m_CurrentPlayer, i_LastMoveColumn, i_LastMoveRow);
            if (rankOfMove >= 4)
            {
                playerWon = true;
            }

            return playerWon;
        }

        public uint? FindRowInAvailableMovesByColumn(uint i_Column)
        {
            uint? rowFounded = null;

            Point PointInAvailableMoves = m_Board.AvailableMoves.Find(point => point.X == (i_Column - 1));
            if (PointInAvailableMoves != null)
            {
                rowFounded = PointInAvailableMoves.Y + 1;
            }

            return rowFounded; 
        }

        public bool SetSquareOnBoard(Player i_Player, uint i_Column, uint i_Row, out string o_MessageIfFailure)
        {
            return m_Board.SetSquare(i_Player, i_Column, i_Row, out o_MessageIfFailure);
        }

        private int rankFutureMove(Player i_Player, Point i_FutureMove)
        {
            int rank = 1;
            int rankOpponent = 1;
            string errorMessage = string.Empty;
            Player OpponentPlayer = getOpponentPlayer(i_Player);

            m_Board.SetSquare(i_Player, i_FutureMove.X + 1, i_FutureMove.Y + 1, out errorMessage);
            rank = rankMove(i_Player, i_FutureMove.X + 1, i_FutureMove.Y + 1);
            m_Board.EmptySquare(i_FutureMove.X + 1, i_FutureMove.Y + 1);

            m_Board.SetSquare(OpponentPlayer, i_FutureMove.X + 1, i_FutureMove.Y + 1, out errorMessage);
            rankOpponent = rankMove(OpponentPlayer, i_FutureMove.X + 1, i_FutureMove.Y + 1);
            m_Board.EmptySquare(i_FutureMove.X + 1, i_FutureMove.Y + 1);

            return Math.Max(rank, rankOpponent);
        }

        private Player getOpponentPlayer(Player i_Player)
        {
            return i_Player.Number == k_PlayerNumber1 ? m_Player2 : m_Player1;
        }

        private int rankMove(Player i_Player, uint i_Width, uint i_Height)
        {
            int highestRank = 1;
            Board.eSquareStatus squareStatusOfPlayer = m_Board.GetSquareStatusByPlayerNumber(i_Player.Number);

            rankColumn(squareStatusOfPlayer, i_Width, i_Height, ref highestRank);
            rankRow(squareStatusOfPlayer, i_Width, i_Height, ref highestRank);
            rankDiagonalLeftToRight(squareStatusOfPlayer, i_Width, i_Height, ref highestRank);
            rankDiagonalRightToLeft(squareStatusOfPlayer, i_Width, i_Height, ref highestRank);

            return highestRank;
        }

        private void rankColumn(Board.eSquareStatus i_SquareStatusOfPlayer, uint i_Width, uint i_Height, ref int io_HighestRank)
        {
            int currentRankOfColumn = 0;

            for (uint i = 0; i < m_Board.Height; i++)
            {
                updateCurrentAndHighestRankByIndexOfMatrix(i_SquareStatusOfPlayer, i_Width - 1, i, ref currentRankOfColumn, ref io_HighestRank);
            }
        }

        private void rankRow(Board.eSquareStatus i_SquareStatusOfPlayer, uint i_Column, uint i_Row, ref int io_HighestRank)
        {
            int currentRankOfRow = 0;

            for (uint i = 0; i < m_Board.Width; i++)
            {
                updateCurrentAndHighestRankByIndexOfMatrix(i_SquareStatusOfPlayer, i, i_Row - 1, ref currentRankOfRow, ref io_HighestRank);
            }
        }

        private void rankDiagonalLeftToRight(Board.eSquareStatus i_SquareStatusOfPlayer, uint i_Column, uint i_Row, ref int io_HighestRank)
        {
            int currentRankOfDiagonal = 0;

            uint startingOffset = Math.Min(i_Column, i_Row);

            for (uint i = i_Column - startingOffset, j = i_Row - startingOffset; i < m_Board.Width && j < m_Board.Height; ++i, ++j)
            {
                updateCurrentAndHighestRankByIndexOfMatrix(i_SquareStatusOfPlayer, i, j, ref currentRankOfDiagonal, ref io_HighestRank);
            }
        }

        private void rankDiagonalRightToLeft(Board.eSquareStatus i_SquareStatusOfPlayer, uint i_Column, uint i_Row, ref int io_HighestRank)
        {
            int currentRankOfDiagonal = 0;

            uint startingOffset = Math.Min(m_Board.Width - i_Column, m_Board.Height - i_Row) - 1;

            for (uint i = i_Column + startingOffset, j = i_Row - startingOffset; i > 0 && j < m_Board.Height; --i, ++j)
            {
                updateCurrentAndHighestRankByIndexOfMatrix(i_SquareStatusOfPlayer, i, j, ref currentRankOfDiagonal, ref io_HighestRank);
            }
        }

        private void updateCurrentAndHighestRankByIndexOfMatrix(Board.eSquareStatus i_SquareStatusOfPlayer, uint i_Column, uint i_Width, ref int io_CurrentRank, ref int io_HighestRank)
        {
            if (m_Board.BoardMatrix[i_Column, i_Width] == i_SquareStatusOfPlayer)
            {
                ++io_CurrentRank;
                io_HighestRank = Math.Max(io_CurrentRank, io_HighestRank);
            }
            else
            {
                io_CurrentRank = 0;
            }
        }
    }
}
